package com.googlecode.jwsm;

import java.sql.*;
import java.text.*;
import java.util.*;
import java.util.logging.*;

import org.jcommon.util.*;

import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.query.Predicate;
import com.db4o.query.Query;

public class DatabaseLogHandler extends Handler {
	public static final Level INIT = new Level("INIT", Level.INFO.intValue()) {};
	public static final Level STACKTRACE = new Level("STACKTRACE", Level.SEVERE.intValue()) {};
	public static final Level DEBUG = new Level("DEBUG", Level.FINE.intValue()) {};

	private static DatabaseLogHandler instance;
	
	public static int MAX_RESULTS = 100;
	private String serverName;
	private ObjectContainer db;

	public DatabaseLogHandler(String serverName, ObjectContainer db) {
		instance = this;
		
		this.serverName = serverName;
		this.db = db;
	}

	public void publish(LogRecord record) {
		try {
			LogEntry entry = new LogEntry(); {
				entry.setThreadId(Thread.currentThread().getId());
				entry.setThreadName(Thread.currentThread().getName());
				entry.setClassName(record.getSourceClassName());
				entry.setMethodName(record.getSourceMethodName());
				if (record.getThrown() == null) {
					entry.setMessage(record.getMessage());
					entry.setLogLevel(record.getLevel().intValue());
					entry.setLogLevelName(record.getLevel().getLocalizedName());
				} else {
					entry.setMessage(StringUtilities.getTextStackTrace(record.getThrown(), true));
					entry.setLogLevel(STACKTRACE.intValue());
					entry.setLogLevelName(STACKTRACE.getLocalizedName());
				}
				entry.setServer(serverName);
				entry.setTimestamp(Calendar.getInstance());
			}
			db.store(entry);
			db.commit();
		} catch(Exception exc) {
			exc.printStackTrace();
		}
	}

	private static final String trimMessage(String message, int length) {
		if (message.length() > length) {
			message = message.substring(0, length);
		}
		return message;
	}

	public static QueryResult queryLogs(final String sortBy, boolean reverse, int offset, final String messageSearch, final String[] threads, final String[] levels, final String[] servers, final Calendar startDate, final Calendar endDate) throws SQLException {
		Predicate<LogEntry> predicate = new Predicate<LogEntry>() {
			private static final long serialVersionUID = 1159453412239270180L;

			public boolean match(LogEntry entry) {
				if (messageSearch != null) {		// handle text search
					if (entry.getMessage().toLowerCase().indexOf(messageSearch.toLowerCase()) == -1) {
						return false;
					}
				}
				
				if ((threads != null) && (threads.length > 0)) {
					boolean found = false;
					for (String thread : threads) {		// handle thread search
						if (thread.equals(entry.getThreadName())) {
							found = true;
							break;
						}
					}
					if (!found) return false;
				}
				
				if ((levels != null) && (levels.length > 0)) {
					boolean found = false;
					for (String level : levels) {		// handle level search
						if (level.equals(entry.getLogLevelName())) {
							found = true;
							break;
						}
					}
					if (!found) return false;
				}
				
				if ((servers != null) && (servers.length > 0)) {
					boolean found = false;
					for (String server : servers) {		// handle server search
						if (server.equals(entry.getServer())) {
							found = true;
							break;
						}
					}
					if (!found) return false;
				}
				
				if (startDate != null) {				// handle start date
					if (entry.getTimestamp().before(startDate)) {
						return false;
					}
				}
				
				if (endDate != null) {					// handle end date
					if (entry.getTimestamp().after(endDate)) {
						return false;
					}
				}
				
				return true;
			}
		};
		Comparator<LogEntry> comparator = new Comparator<LogEntry>() {
			public int compare(LogEntry o1, LogEntry o2) {
				if ("stamp".equals(sortBy)) {
					return o1.getTimestamp().compareTo(o2.getTimestamp());
				} else if ("threadName".equals(sortBy)) {
					return o1.getThreadName().compareTo(o2.getThreadName());
				} else if ("className".equals(sortBy)) {
					return o1.getClassName().compareTo(o2.getClassName());
				} else if ("methodName".equals(sortBy)) {
					return o1.getMethodName().compareTo(o2.getMethodName());
				} else if ("level".equals(sortBy)) {
					return o1.getLogLevelName().compareTo(o2.getLogLevelName());
				} else if ("message".equals(sortBy)) {
					return o1.getMessage().compareTo(o2.getMessage());
				} else if ("server".equals(sortBy)) {
					return o1.getServer().compareTo(o2.getServer());
				} else {
					System.err.println("*** Unknown sort: " + sortBy);
				}
				return 0;
			}
		};
		ObjectSet<LogEntry> results = instance.db.query(predicate, comparator);
		
		int total = 0;
		List<LogEntry> list = new ArrayList<LogEntry>();
		while (results.hasNext()) {
			LogEntry entry = results.next();
			total++;
			if (offset > 0) {
				offset--;
				continue;
			}
			if (list.size() < MAX_RESULTS) {
				list.add(entry);
			}
		}
		
		if (reverse) {		// Reverse the list
			Collections.reverse(list);
		}
		
		QueryResult qr = new QueryResult(); {
			qr.results = list;
			qr.totalLogs = total;
		}
		return qr;
	}

	private static Set<String> threads;
	private static Set<String> levels;
	private static Set<String> servers;
	
	public static void updateSets() {
		threads = new HashSet<String>();
		levels = new HashSet<String>();
		servers = new HashSet<String>();
		ObjectSet<LogEntry> results = instance.db.query(LogEntry.class);
		while (results.hasNext()) {
			LogEntry entry = results.next();
			if (entry.getThreadName() != null) {
				threads.add(entry.getThreadName());
			}
			if (entry.getLogLevelName() != null) {
				levels.add(entry.getLogLevelName());
			}
			if (entry.getServer() != null) {
				servers.add(entry.getServer());
			}
		}
	}
	
	public static Set<String> queryThreadFilters() throws SQLException {
		return threads;
	}

	public static Set<String> queryLevelFilters() throws SQLException {
		return levels;
	}

	public static Set<String> queryServerFilters() throws SQLException {
		return servers;
	}

	public static int purgeLogs() throws SQLException {
		ObjectSet<LogEntry> results = instance.db.query(LogEntry.class);
		int deleted = 0;
		while (results.hasNext()) {
			LogEntry entry = results.next();
			instance.db.delete(entry);
			deleted++;
		}
		instance.db.commit();
		return deleted;
	}

	/*public static boolean deleteDatabase() {
		try {
			Uploader.shutdownLogger();
			if (FileUtilities.deltree(new File(Uploader.directory, ".database"))) {
				return true;
			}
		} catch(Exception exc) {
			exc.printStackTrace();
		} finally {
			try {
				Uploader.reinit();
			} catch(Exception exc0) {
				exc0.printStackTrace();
			}
		}
		return false;
	}*/

	public void flush() {
	}

	public void close() throws SecurityException {
		instance.db.close();
	}
}
